<?php

class My_Logger
{
    // Log levels
    const DUMP = -1;
    const SUCCESS = 0;
    const INFO = 1;
    const WARNING = 2;
    const ERROR = 3;

    /**
     * Log level names
     * @var array
     */
    protected $logLevelNames = array(
      My_Logger::DUMP => 'DUMP',
      My_Logger::SUCCESS => 'SUCCESS',
      My_Logger::INFO => 'INFO',
      My_Logger::WARNING => 'WARNING',
      My_Logger::ERROR => 'ERROR'
    );

    /**
     * Stack 
     * @var type 
     */
    protected $steps = array();
    
    /**
     * @var string
     */
    protected $system = '';

    /**
     * @var array
     */
    protected $environment = array();
    
    /**
     * @var boolean
     */
    protected $verbose = false;

    /**
     * @var string
     */
    protected $outputFile;
    
    /**
     * Internal instance of My_Logger
     * @var My_Logger
     */
    static $instance;

    /**
     * Load an instance of My_Logger (singleton)
     * @return My_Logger
     */
    public static function getInstance()
    {
        if (!(self::$instance instanceof My_Logger))
        {
            self::$instance = new My_Logger();
        }
        return self::$instance;
    }

    /**
     * Alias of getInstance()
     * @return My_Logger 
     */
    public static function i()
    {
        return self::getInstance();
    }
    
    /**
     * Default protected constructor (@see singleton interface).
     */
    protected function __construct()
    {
        $this->outputFile = dirname(APPLICATION_PATH) . DIRECTORY_SEPARATOR . 'application'.DIRECTORY_SEPARATOR.'logs'.DIRECTORY_SEPARATOR. date('Y-m-d') . '.log';
        $this->reset();
    }

    /**
     *
     * @param boolean $system
     * @param boolean $steps
     * @param boolean $environment
     * @return My_Logger 
     */
    public function reset($system = true, $steps = true, $environment = true)
    {
        if (true == $system) $this->resetSystem ();
        if (true == $steps) $this->resetSteps ();
        if (true == $environment) $this->resetEnvironment ();
        return $this;
    }
    
    /**
     * @param string $systemName
     * @return My_Logger 
     */
    public function setSystem($systemName)
    {
        $this->system = $systemName;
        return $this;
    }
    
    /**
     * @return My_Logger 
     */
    public function resetSystem()
    {
        $this->system = '';
        return $this;
    }


    /**
     * @param array $environment
     * @return My_Logger 
     */
    public function setEnvironment($environment)
    {
        if (is_array($environment) && count($environment)>0) $this->environment = $environment;
        return $this;
    }
    
    /**
     * @return My_Logger 
     */
    public function resetEnvironment()
    {
        $this->environment = array();
        return $this;
    }
    
    /**
     * @param string $label
     * @param string $value
     * @return My_Logger 
     */
    public function addToEnvironment($label, $value)
    {
        $this->environment[$label] = $value;
        return $this;
    }
    
    /**
     * @param string $label
     * @return My_Logger 
     */
    public function removeFromEnvironment($label)
    {
        if (array_key_exists($label, $this->environment)) unset ($this->environment[$label]);
        return $this;
    }

    /**
     * @param string $stepName
     * @return My_Logger 
     */
    public function stepIn($stepName)
    {
        array_push($this->steps, $stepName);
        return $this;
    }

    /**
     * @return My_Logger 
     */
    public function stepOut()
    {
        array_pop($this->steps);
        return $this;
    }

    /**
     * @return My_Logger 
     */
    public function resetSteps()
    {
        $this->steps = array();
        return $this;
    }
    
    /**
     * @param boolean $verbose 
     * @return My_Logger
     */
    public function setVerbose($verbose = true)
    {
        $this->verbose = $verbose;
        return $this;
    }

    /**
     * Writes log msgs in a file.
     * @param integer $level
     * @param string $msg
     * @param array $extra
     * @param boolean $verbose
     */
    protected function logToFile($level, $msg, $extra = null)
    {
        if (is_array($extra) && count($extra)>0) $msg = vsprintf($msg, $this->parseTypes($extra));
        $msg = sprintf("%s [%s] [SYSTEM:%s] [ENV:%s] %s\n", date('Y-m-d H:i:s'), str_pad($this->logLevelNames[$level], 10, '.', STR_PAD_RIGHT), $this->implodeSteps(), $this->implodeEnvironment(), $msg);
        if (true === $this->verbose) print $msg;
        $fileHandler = fopen($this->outputFile, 'a');
        fwrite($fileHandler, $msg);
        fclose($fileHandler);
    }

    /**
     * @param mixed $var
     * @param string $label
     * @return My_Logger
     */
    public function dump($var, $label = 'DUMP')
    {
        $this->logToFile(My_Logger::DUMP, 
            sprintf("\n--------------------------------------------------------------------------\n%s: \n%s\n--------------------------------------------------------------------------", strtoupper($label), var_export($var, true)));
        return $this;
    }

    /**
     * @param string $msg
     * @return My_Logger
     */
    public function info($msg)
    {
        $extra = null;
        $args = func_get_args();
        if (count($args)>1)
        {
            array_shift($args);
            $extra = $args;
        }
        $this->logToFile(My_Logger::INFO, $msg, $extra);
        return $this;
    }

    /**
     * @param string $msg
     * @return My_Logger
     */
    public function success($msg)
    {
        $extra = null;
        $args = func_get_args();
        if (count($args)>1)
        {
            array_shift($args);
            $extra = $args;
        }
        $this->logToFile(My_Logger::SUCCESS, $msg, $extra);
        return $this;
    }

    /**
     * @param string $msg
     * @return My_Logger
     */
    public function warning($msg)
    {
        $extra = null;
        $args = func_get_args();
        if (count($args)>1)
        {
            array_shift($args);
            $extra = $args;
        }
        $this->logToFile(My_Logger::WARNING, $msg, $extra);
        return $this;
    }

    /**
     * @param string $msg
     * @return My_Logger
     */
    public function error($msg)
    {
        $extra = null;
        $args = func_get_args();
        if (count($args)>1)
        {
            array_shift($args);
            $extra = $args;
        }
        $this->logToFile(My_Logger::ERROR, $msg, $extra);
        return $this;
    }
    
    /**
     * @param string $msg
     * @return My_Logger
     */
    public function separator()
    {
        $fileHandler = fopen($this->outputFile, 'a');
        fwrite($fileHandler, '--------------------------------------------------------------------------'.PHP_EOL);
        fclose($fileHandler);
        return $this;
    }

    /**
     * @return string
     */
    private function implodeEnvironment()
    {
        $envInfo = array();
        $envString = 'NONE';
        if (is_array($this->environment) && count($this->environment)>0)
        {
            foreach ($this->environment as $key => $value)
            {
                if (is_array($value)) $value = Zend_Json::encode ($value);
                elseif (is_bool($value)) $value = (true == $value) ? 'true' : 'false';
                elseif ($value instanceof Zend_Date) $value = $value->get('yyyy-M-d');
                $envInfo[] = sprintf('(%s=%s)', $key, $value);
            }
            $envString = implode('', $envInfo);
        }
        return $envString;
    }
    
    /**
     * @return string
     */
    private function implodeSteps()
    {
        $fullSteps = $this->steps;
        array_unshift($fullSteps, $this->system);
        return implode('/', $fullSteps);
    }
    
    /**
     * @param array $array
     * @return array 
     */
    private function parseTypes($toParseTypes)
    {
        $parsed = array();
        if (is_array($toParseTypes) && count($toParseTypes)>0)
        {
            foreach ($toParseTypes as $key => $value)
            {
                if (is_array($value)) $value = Zend_Json::encode ($value);
                elseif (is_bool($value)) $value = (true == $value) ? 'true' : 'false';
                elseif ($value instanceof Zend_Date) $value = $value->get(Zend_Date::ISO_8601);
                $parsed[$key] = $value;
            }
        }
        return $parsed;
    }
}